/* MD5F.C */

/*
 * Simple MD5 implementation
 * taken from https://gist.github.com/creationix/4710780
 */

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifdef HAS_64BIT
#include <stdint.h>
#else /* HAS_64BIT */
typedef unsigned char uint8_t;
#if INT_MAX == 32767
typedef unsigned long uint32_t;
#else /* INT_MAX */
typedef unsigned int uint32_t;
#endif /* INT_MAX */
#endif /* HAS_64BIT */


#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c))))

int r[] = {
			7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
			5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20,
			4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
			6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
};

uint32_t k[] = {
				0xd76aa478UL, 0xe8c7b756UL, 0x242070dbUL, 0xc1bdceeeUL,
				0xf57c0fafUL, 0x4787c62aUL, 0xa8304613UL, 0xfd469501UL,
				0x698098d8UL, 0x8b44f7afUL, 0xffff5bb1UL, 0x895cd7beUL,
				0x6b901122UL, 0xfd987193UL, 0xa679438eUL, 0x49b40821UL,
				0xf61e2562UL, 0xc040b340UL, 0x265e5a51UL, 0xe9b6c7aaUL,
				0xd62f105dUL, 0x02441453UL, 0xd8a1e681UL, 0xe7d3fbc8UL,
				0x21e1cde6UL, 0xc33707d6UL, 0xf4d50d87UL, 0x455a14edUL,
				0xa9e3e905UL, 0xfcefa3f8UL, 0x676f02d9UL, 0x8d2a4c8aUL,
				0xfffa3942UL, 0x8771f681UL, 0x6d9d6122UL, 0xfde5380cUL,
				0xa4beea44UL, 0x4bdecfa9UL, 0xf6bb4b60UL, 0xbebfbc70UL,
				0x289b7ec6UL, 0xeaa127faUL, 0xd4ef3085UL, 0x04881d05UL,
				0xd9d4d039UL, 0xe6db99e5UL, 0x1fa27cf8UL, 0xc4ac5665UL,
				0xf4292244UL, 0x432aff97UL, 0xab9423a7UL, 0xfc93a039UL,
				0x655b59c3UL, 0x8f0ccc92UL, 0xffeff47dUL, 0x85845dd1UL,
				0x6fa87e4fUL, 0xfe2ce6e0UL, 0xa3014314UL, 0x4e0811a1UL,
				0xf7537e82UL, 0xbd3af235UL, 0x2ad7d2bbUL, 0xeb86d391UL
};


uint32_t h0, h1, h2, h3;


void one_round(uint32_t *w)
{
	uint32_t a = h0;
	uint32_t b = h1;
	uint32_t c = h2;
	uint32_t d = h3;

	uint32_t i;
	for (i = 0; i < 64; i++) {
		uint32_t f, g;

		 if (i < 16) {
			f = (b & c) | ((~b) & d);
			g = i;
		} else if (i < 32) {
			f = (d & b) | ((~d) & c);
			g = (5*i + 1) % 16;
		} else if (i < 48) {
			f = b ^ c ^ d;
			g = (3*i + 5) % 16;
		} else {
			f = c ^ (b | (~d));
			g = (7*i) % 16;
		}

		{
			uint32_t temp = d;
			d = c;
			c = b;
			b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]);
			a = temp;
		}
	}

	h0 += a;
	h1 += b;
	h2 += c;
	h3 += d;
}

union ull_u {
	struct ull_s {
		uint32_t lop;
		uint32_t hip;
	} sv;
#ifdef HAS_64BIT
	uint64_t nv;
#endif /* HAS_64BIT */
};

void ull_add32(union ull_u *u, uint32_t v)
{
#ifdef HAS_64BIT
	u->nv += v;
#else /* HAS_64BIT */
	uint32_t d = 0xffffffffUL - u->sv.lop;
	if (d < v) {
		u->sv.hip += 1;
		u->sv.lop = v - d - 1;
	} else {
		u->sv.lop += v;
	}
#endif /* HAS_64BIT */
}

void ull_shift(union ull_u *u, int s)
{
#ifdef HAS_64BIT
	u->nv <<= s;
#else /* HAS_64BIT */
	int i;
	for (i = 0; i < s; i++) {
		u->sv.hip <<= 1;

		if (u->sv.lop & 0x80000000UL)
			u->sv.hip |= 1;

		u->sv.lop <<= 1;
	}
#endif /* HAS_64BIT */
}

int ull_great(union ull_u *a, union ull_u *b)
{
#ifdef HAS_64BIT
	return (a->nv > b->nv);
#else /* HAS_64BIT */
	return (a->sv.hip > b->sv.hip ||
		(a->sv.hip == b->sv.hip && a->sv.lop > b->sv.lop));
#endif /* HAS_64BIT */
}

uint32_t ull_delta(union ull_u *a, union ull_u *b)
{
#ifdef HAS_64BIT
	return (uint32_t)(a->nv - b->nv);
#else /* HAS_64BIT */
	if (a->sv.lop >= b->sv.lop)
		return (a->sv.lop - b->sv.lop);
	return (0xffffffffUL - b->sv.lop + a->sv.lop + 1);
#endif /* HAS_64BIT */
}

void ull_add(union ull_u *a, union ull_u *b)
{
#ifdef HAS_64BIT
	a->nv += b->nv;
#else /* HAS_64BIT */
	ull_add32(a, b->sv.lop);
	a->sv.hip += b->sv.hip;
#endif /* HAS_64BIT */
}

void ull_fromstr(union ull_u *u, const char *str)
{
#ifdef HAS_64BIT
	u->nv = strtoull(str, NULL, 0);
#else /* HAS_64BIT */
	uint32_t base;
	int len, i;

	/* $ TENPOWER=1; for i in `seq 1 20`; do echo "obase=16;$TENPOWER" | bc; TENPOWER="${TENPOWER}0"; done */
	static union ull_u tenpowers[] = {
		{{0x1UL, 0x0UL}},
		{{0xaUL, 0x0UL}},
		{{0x64UL, 0x0UL}},
		{{0x3e8UL, 0x0UL}},
		{{0x2710UL, 0x0UL}},
		{{0x186a0UL, 0x0UL}},
		{{0xf4240UL, 0x0UL}},
		{{0x989680UL, 0x0UL}},
		{{0x5f5e100UL, 0x0UL}},
		{{0x3b9aca00UL, 0x0UL}},
		{{0x540be400UL, 0x2UL}},
		{{0x4876e800UL, 0x17UL}},
		{{0xd4a51000UL, 0xe8UL}},
		{{0x4e72a000UL, 0x918UL}},
		{{0x107a4000UL, 0x5af3UL}},
		{{0xa4c68000UL, 0x38d7eUL}},
		{{0x6fc10000UL, 0x2386f2UL}},
		{{0x5d8a0000UL, 0x1634578UL}},
		{{0xa7640000UL, 0xde0b6b3UL}},
		{{0x89e80000UL, 0x8ac72304UL}}
	};

	if (*str == '0') {
		str++;
		if (*str == 'x' || *str == 'X') {
			base = 16;
			str++;
		} else {
			base = 8;
		}
	} else {
		base = 10;
	}

	u->sv.lop = u->sv.hip = 0;

	len = (int)strlen(str);
	for (i = 0; i < len; i++) {
		union ull_u pos = {{0,0}};
		int ch = toupper(str[len - 1 - i]);

		if (!((ch >= '0' && ch <= '7') ||
			(base > 8 && (ch == '8' || ch == '9')) ||
			(base == 16 && ch >= 'A' && ch <= 'F')))
			break;

		ch -= (ch > '9' ? '7' : '0');

		if (base == 16) {
			pos.sv.lop = ch;
			ull_shift(&pos, 4 * i);
		} else if (base == 8) {
			pos.sv.lop = ch;
			ull_shift(&pos, 3 * i);
		} else {
			if ((size_t)i >= sizeof (tenpowers) / sizeof (tenpowers[0])) break;

			while (ch--) ull_add(&pos, &tenpowers[i]);
		}

		ull_add(u, &pos);
	}
#endif /* HAS_64BIT */
}

int md5f(FILE *fd, union ull_u *hint)
{
	uint8_t msg[128];
	union ull_u total_len;

	total_len.sv.lop = total_len.sv.hip = 0;

	h0 = 0x67452301UL;
	h1 = 0xefcdab89UL;
	h2 = 0x98badcfeUL;
	h3 = 0x10325476UL;

	memset(msg, 0, sizeof(msg));

	for (; ;) {
		uint32_t toread = 64, count;

		memset(msg, 0, 64);

		if (hint) {
			union ull_u nc = total_len;
			ull_add32(&nc, 64);
			toread = (ull_great(hint, &nc) ? 64 : ull_delta(hint, &total_len));
		}

		count =(uint32_t)fread(msg, 1, toread, fd);

		ull_add32(&total_len, count);

		if (count < 64) {
			ull_shift(&total_len, 3);

			msg[count] = 128;

			if (count < 56) {
				memcpy(msg + 56, &total_len, sizeof (total_len));
				one_round((uint32_t *)msg);
			} else {
				memcpy(msg + 64 + 56, &total_len, sizeof (total_len));
				one_round((uint32_t *)msg);
				one_round((uint32_t *)(msg + 64));
			}

			break;

		} else {
			one_round((uint32_t *)msg);
		}
	}

	return (0);
}
 
int main(int argc, char **argv)
{
	FILE *fd;
	int res = 0;
	uint8_t *p;
	union ull_u hint;

	hint.sv.lop = hint.sv.hip = 0;

	if (argc == 1) {
		res = md5f(stdin, NULL);
	} else {
		fd = fopen(argv[1],"rb");

		if (!fd) {
			fprintf(stderr, "couldn't open %s (%d)\n", argv[1], errno);
			return (1);
		}

		if (argc > 2) {
			ull_fromstr(&hint, argv[2]);
			res = md5f(fd, &hint);
		} else {
			res = md5f(fd, NULL);
		}

		fclose(fd);
	}

	if (!res) {
		p=(uint8_t *)&h0;
		printf("%2.2x%2.2x%2.2x%2.2x", p[0], p[1], p[2], p[3]);
	 
		p=(uint8_t *)&h1;
		printf("%2.2x%2.2x%2.2x%2.2x", p[0], p[1], p[2], p[3]);
	 
		p=(uint8_t *)&h2;
		printf("%2.2x%2.2x%2.2x%2.2x", p[0], p[1], p[2], p[3]);
	 
		p=(uint8_t *)&h3;
		printf("%2.2x%2.2x%2.2x%2.2x", p[0], p[1], p[2], p[3]);
		puts("");
	}

	return (res);
}
/* end */

/*
cl -c -W3 -Y- -D_CRT_SECURE_NO_WARNINGS -O2 -DNDEBUG -Zi MD5F.C
link -out:md5f.exe -map -debug -PDB:MD5F.pdb MD5F.obj -subsystem:console,5.01
*/
